from enum import Enum
from typing import List, Dict, Tuple
import sys
import logging as l

# 参数模块
class parameter:
    def __init__(self, migrate_c_threshold: int, migrate_m_threshold: int, migrate_signal_max_threshold: int, migrate_double_max_threshold: int,
                put_c_threshold: int, put_m_threshold: int, put_signal_max_threshold: int, put_double_max_threshold: int,
                leftmargin: float, rightmargin: float):
        self.migrate_c_threshold = migrate_c_threshold
        self.migrate_m_threshold = migrate_m_threshold
        self.migrate_signal_max_threshold = migrate_signal_max_threshold
        self.migrate_double_max_threshold = migrate_double_max_threshold
        self.put_c_threshold = put_c_threshold
        self.put_m_threshold = put_m_threshold
        self.put_signal_max_threshold = put_signal_max_threshold
        self.put_double_max_threshold = put_double_max_threshold
        self.leftmargin = leftmargin
        self.rightmargin = rightmargin

# 类型模块
class Servertype(object):
    def __init__(self, name, core, memory, hardware_price, daily_cost):
        self.name = name
        self.core = core
        self.memory = memory
        self.hardware_price = hardware_price
        self.daily_cost = daily_cost

    # 计算物理机型号的性价比
    def cost_performance(self):
        return float((self.core * 0.5 + self.memory * 0.5) / (self.hardware_price+0.4*self.daily_cost) * 100)

    def __repr__(self) -> str:
        return 'Servertype(%s, %d, %d, %d, %d)' % (
        self.name, self.core, self.memory, self.hardware_price, self.daily_cost)

    def __str__(self) -> str:
        return self.__repr__()


class Vmtype(object):
    def __init__(self, name, core, memory, is_double):
        self.name = name
        self.core = core
        self.memory = memory
        self.is_double = is_double

    def __repr__(self):
        return 'Vmtype(%s, %d, %d, %d)' % (self.name, self.core, self.memory, self.is_double)

    def __str__(self) -> str:
        return self.__repr__()


class Operation(object):
    class Op(Enum):
        ADD = 1  # 添加虚拟机操作
        DEL = 2  # 删除虚拟机操作

    def __init__(self, mode: Op, vmid, vm_type_id=None):
        self.mode = mode
        self.vmid = vmid
        self.vm_type_id = vm_type_id

    def __repr__(self):
        return 'Operation(%s, %s, %s)' % (self.mode, self.vmid, self.vm_type_id)
    def __str__(self) -> str:
        return self.__repr__()


class ServerInstance(object):
    instance_index = 0

    def __init__(self, servertype: Servertype, server_id):
        self.type = servertype
        self.a_core_remain = servertype.core // 2
        self.b_core_remain = self.a_core_remain
        self.a_memory_remain = servertype.memory // 2
        self.b_memory_remain = self.a_memory_remain
        self.server_id = server_id
        self.record = dict()  # 用于记录分配时虚拟机分配到哪一个核: 'both','A','B'

    def __repr__(self):
        return 'Instance(am<%d>,bm<%d>,ac<%d>,bc<%d>=<%d,%d>)' % (self.a_memory_remain,
                                                                  self.b_memory_remain,
                                                                  self.a_core_remain,
                                                                  self.b_core_remain,
                                                                  self.remain_memory(),
                                                                  self.remain_core())



    def check_consistency(self):
        """检查这个实例的一致性"""
        instance = self
        server_type = self.type
        assert (instance.a_memory_remain * 2 <= server_type.memory)
        assert (instance.b_memory_remain * 2 <= server_type.memory)
        assert (instance.a_core_remain * 2 <= server_type.core)
        assert (instance.b_core_remain * 2 <= server_type.core)
        assert (instance.a_memory_remain >= 0)
        assert (instance.b_memory_remain >= 0)
        assert (instance.a_core_remain >= 0)
        assert (instance.b_core_remain >= 0)

    def empty(self):
        return len(self.record) == 0

    def remain_memory(self):
        return self.b_memory_remain + self.a_memory_remain

    def remain_core(self):
        return self.b_core_remain + self.a_core_remain

    def total_usage(self):
        return 0.4*self.remain_memory() + 0.6*self.remain_core()

    def min_memory(self):
        return min(self.a_memory_remain, self.b_memory_remain)

    def min_core(self):
        return min(self.a_core_remain, self.b_core_remain)

    # 一些统计信息
    def remain_memory_ratio(self):
        """返回当前实例的memory剩余比例，[0,1]之间的小数"""
        return (self.a_memory_remain+self.b_memory_remain) / self.type.memory

    def remain_core_ratio(self):
        """返回当前实例的core剩余比例，[0,1]之间的小数"""
        return (self.a_core_remain+self.b_core_remain) / self.type.core

    def can_fit(self, vmtype: Vmtype):
        """判断能否把虚拟机放入到物理机server_instance中"""
        """如果能放下返回当前物理机节点剩余的情况"""
        is_double = vmtype.is_double

        if is_double:
            memory_fit = False
            core_fit = False
            if self.a_memory_remain >= vmtype.memory // 2 and \
                    self.b_memory_remain >= vmtype.memory // 2:
                memory_fit = True
            if self.a_core_remain >= vmtype.core // 2 and \
                    self.b_core_remain >= vmtype.core // 2:
                core_fit = True
            return memory_fit and core_fit
        else:
            # 放在A
            if self.a_memory_remain >= vmtype.memory and self.a_core_remain >= vmtype.core:
                return True
            # 放在B
            if self.b_memory_remain >= vmtype.memory and self.b_core_remain >= vmtype.core:
                return True
            return False

    def can_fit_a(self, vmtype:Vmtype):
        assert(not vmtype.is_double)
        return vmtype.memory <= self.a_memory_remain and vmtype.core <= self.a_core_remain

    def can_fit_b(self, vmtype:Vmtype):
        assert(not vmtype.is_double)
        return vmtype.memory <= self.b_memory_remain and vmtype.core <= self.b_core_remain

    def try_fit_appointed_node(self, vmtype: Vmtype,node):
        """尝试把虚拟机分配到指定节点"""
        is_double = vmtype.is_double
        if is_double and node=="both":
            if self.a_memory_remain < vmtype.memory // 2 or \
                    self.b_memory_remain < vmtype.memory // 2 or \
                    self.a_core_remain < vmtype.core // 2 or \
                    self.b_core_remain < vmtype.core // 2:
                l.fatal(
                    'this is unexpected. failed with vm(%s) and instance(%s)' % (vmtype, self))
                sys.exit(-1)
            self.a_core_remain -= vmtype.core // 2
            self.b_core_remain -= vmtype.core // 2
            self.a_memory_remain -= vmtype.memory // 2
            self.b_memory_remain -= vmtype.memory // 2
            node = 'both'
        elif is_double == 0 and node!="both":
            if node == 'A' and self.can_fit_a(vmtype):
                self.a_memory_remain -= vmtype.memory
                self.a_core_remain -= vmtype.core
            elif node == 'B' and self.can_fit_b(vmtype):
                self.b_memory_remain -= vmtype.memory
                self.b_core_remain -= vmtype.core
            else:
                l.fatal(
                    'this is unexpected. try_fit() failed with vm(%s) and instance(%s) and it a remain core is(%s) a remain memory is(%s),b remain core is(%s) b remain memory is(%s) node=(%s)' \
                    % (vmtype, self, self.a_core_remain, self.a_memory_remain, self.b_core_remain, self.b_memory_remain,
                       node))
                sys.exit(-1)
        else:
            l.fatal(
                'this is unexpected. try_fit() failed with vm(%s) and instance(%s) and it a remain core is(%s) a remain memory is(%s),b remain core is(%s) b remain memory is(%s) node=(%s)' \
                    % (vmtype, self,self.a_core_remain,self.a_memory_remain,self.b_core_remain,self.b_memory_remain,node))
            sys.exit(-1)
        return node

    def try_fit(self, vmtype: Vmtype):
        """尝试把虚拟机放到服务器实例中
        返回分配到哪个结点: "both","A","B"
        """
        node = None

        if not self.can_fit(vmtype): return None
        is_double = vmtype.is_double

        if is_double:
            if self.a_memory_remain < vmtype.memory // 2 or \
                    self.b_memory_remain < vmtype.memory // 2 or \
                    self.a_core_remain < vmtype.core // 2 or \
                    self.b_core_remain < vmtype.core // 2:
                l.fatal(
                    'this is unexpected. try_fit() failed with vm(%s) and instance(%s)' % (vmtype, self))
                sys.exit(-1)
            self.a_core_remain -= vmtype.core // 2
            self.b_core_remain -= vmtype.core // 2
            self.a_memory_remain -= vmtype.memory // 2
            self.b_memory_remain -= vmtype.memory // 2
            node = 'both'
        else:
            # TODO 选A结点还是B结点
            node = None
            A_first = True
            if not A_first:
                if self.can_fit_a(vmtype) and self.can_fit_b(vmtype):
                    if self.a_memory_remain > self.b_memory_remain:
                        node = 'A'
                    else:
                        node = 'B'
                elif self.can_fit_a(vmtype):
                    node = 'A'
                elif self.can_fit_b(vmtype):
                    node = 'B'
            else:
                if self.can_fit_a(vmtype):
                    node = 'A'
                elif self.can_fit_b(vmtype):
                    node = 'B'

            assert (node is not None)
            if node == 'A':
                self.a_memory_remain -= vmtype.memory
                self.a_core_remain -= vmtype.core
            else:
                self.b_memory_remain -= vmtype.memory
                self.b_core_remain -= vmtype.core
        return node

    def allocate(self, vmtype: Vmtype, vmid):
        """分配虚拟机到当前实例
        返回存储到物理机的哪个结点: 'both','A','B'
        如果无法分配，返回None
        """
        node = self.try_fit(vmtype)
        if node is None:
            sys.exit(-1)
        if node is not None:
            self.record[vmid] = node
        return node

    def allocate_appoint_node(self, vmtype : Vmtype, vmid, node):
        """指定把某个虚拟机放在该物理机的某个节点"""
        self.try_fit_appointed_node(vmtype, node)
        self.record[vmid] = node
        return node

    def free(self, vmtype: Vmtype, vmid):
        """
        释放虚拟机的资源
        :param vmtype:
        :param vmid:
        :return:
        """
        if vmid not in self.record:
            l.fatal('Try to free a vm that is not inside this instance')
            sys.exit(-1)

        if vmtype.is_double:
            self.a_core_remain += vmtype.core // 2
            self.b_core_remain += vmtype.core // 2
            self.a_memory_remain += vmtype.memory // 2
            self.b_memory_remain += vmtype.memory // 2
        else:
            if self.record[vmid] == 'A':
                self.a_memory_remain += vmtype.memory
                self.a_core_remain += vmtype.core
            elif self.record[vmid] == 'B':
                self.b_memory_remain += vmtype.memory
                self.b_core_remain += vmtype.core
            else:
                l.fatal('this is unexpected')
                sys.exit(-1)
        self.record.pop(vmid)

    @staticmethod
    def get_instance(servertype: Servertype):
        return ServerInstance(servertype, None)

    @staticmethod
    def next_index():
        r = ServerInstance.instance_index
        ServerInstance.instance_index += 1
        return r
